using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using TelnetExpect;
using System.IO;

namespace TelnetTests.Ansi
{
    [TestFixture]
    public class ControlKeysTests
    {
        public ControlKeysTests()
        {
            TestsBase.EnsureConsoleTraceListener();
        }

        private void DoTest(ConsoleKey consoleKey, string expected)
        {
            string name = consoleKey.ToString();
            //
            MemoryStream dst = Execute(consoleKey);
            string result = Encoding.ASCII.GetString(dst.ToArray());
            Assert.AreEqual(expected, result, name);
            TestsBase.Assert_Buffers_AreEqual(Encoding.ASCII.GetBytes(expected), dst, name + " (bytes)");
        }

        private static MemoryStream Execute(ConsoleKey consoleKey)
        {
            AnsiConsoleDut dut = new AnsiConsoleDut();
            MemoryStream dst = new MemoryStream();
            StreamWriter wtr = new StreamWriter(dst, Encoding.ASCII);
            //
            dst.SetLength(0);
            ConsoleKeyInfo cki = new ConsoleKeyInfo((char)0, consoleKey, false, false, false);
            dut.WriteKeyPressTo(cki, true, wtr);
            wtr.Flush();
            return dst;
        }

        private void DoTest(char ch, string expected)
        {
            string name = ch.ToString();
            //
            MemoryStream dst = Execute(ch);
            string result = Encoding.ASCII.GetString(dst.ToArray());
            Assert.AreEqual(expected, result, name);
            TestsBase.Assert_Buffers_AreEqual(Encoding.ASCII.GetBytes(expected), dst, name + " (bytes)");
        }

        private static MemoryStream Execute(char ch)
        {
            AnsiConsoleDut dut = new AnsiConsoleDut();
            MemoryStream dst = new MemoryStream();
            StreamWriter wtr = new StreamWriter(dst, Encoding.ASCII);
            //
            dst.SetLength(0);
            ConsoleKeyInfo cki = new ConsoleKeyInfo(ch, 0, false, false, false);
            dut.WriteKeyPressTo(cki, true, wtr);
            wtr.Flush();
            return dst;
        }

        const string ESC = "\x001b";
        const string CSI = ESC + "[";
        const string Char_07_14_Tilde = "~";

        //---------------------------
        [Test]
        public void Alpha()
        {
            DoTest('a', "a");
        }

        //---------------------------
        [Test]
        public void NotSupportedControlKey()
        {
            DoTest(ConsoleKey.LeftWindows, "LeftWindows");
        }

        [Test]
        public void Enter()
        {
            DoTest(ConsoleKey.Enter, "\x000D\x000A");
        }

        [Test]
        public void F1toF4()
        {
            DoTest(ConsoleKey.F1, ESC + "OP");
            DoTest(ConsoleKey.F2, ESC + "OQ");
            DoTest(ConsoleKey.F3, ESC + "OR");
            DoTest(ConsoleKey.F4, ESC + "OS");
        }

        [Test]
        public void ArrowKeys()
        {
            DoTest(ConsoleKey.UpArrow, CSI + "A");
            DoTest(ConsoleKey.DownArrow, CSI + "B");
            DoTest(ConsoleKey.RightArrow, CSI + "C");
            DoTest(ConsoleKey.LeftArrow, CSI + "D");
        }

        [Test]
        public void HomeInsertEtc()
        {
            DoTest(ConsoleKey.Home, CSI + "1" + Char_07_14_Tilde);
            DoTest(ConsoleKey.Insert, CSI + "2" + Char_07_14_Tilde);
            DoTest(ConsoleKey.Delete, CSI + "3" + Char_07_14_Tilde);
            DoTest(ConsoleKey.End, CSI + "4" + Char_07_14_Tilde);
            DoTest(ConsoleKey.PageUp, CSI + "5" + Char_07_14_Tilde);
            DoTest(ConsoleKey.PageDown, CSI + "6" + Char_07_14_Tilde);
        }

        [Test]
        public void Numpad_Numerals()
        {
            DoTest(ConsoleKey.NumPad0, ESC + "Op");
            DoTest(ConsoleKey.NumPad1, ESC + "Oq");
            DoTest(ConsoleKey.NumPad2, ESC + "Or");
            DoTest(ConsoleKey.NumPad3, ESC + "Os");
            DoTest(ConsoleKey.NumPad4, ESC + "Ot");
            DoTest(ConsoleKey.NumPad5, ESC + "Ou");
            DoTest(ConsoleKey.NumPad6, ESC + "Ov");
            DoTest(ConsoleKey.NumPad7, ESC + "Ow");
            DoTest(ConsoleKey.NumPad8, ESC + "Ox");
            DoTest(ConsoleKey.NumPad9, ESC + "Oy");
        }

        [Test]
        public void Numpad_Others()
        {
            DoTest(ConsoleKey.Multiply, ESC + "Oj");
            DoTest(ConsoleKey.Add, ESC + "Ok");
            //DoTest(ConsoleKey.COMMA, ESC + "Ol", null);
            DoTest(ConsoleKey.Subtract, ESC + "Om");
            DoTest(ConsoleKey.Decimal, ESC + "On");
            DoTest(ConsoleKey.Divide, ESC + "Oo");
        }

        [Test]
        public void F5toFx()
        {
            DoTest(ConsoleKey.F5, CSI + "15" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F6, CSI + "17" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F7, CSI + "18" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F8, CSI + "19" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F9, CSI + "20" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F10, CSI + "21" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F11, CSI + "23" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F12, CSI + "24" + Char_07_14_Tilde);
        }

        //[Test]
        public void F13toFx()
        {
            DoTest(ConsoleKey.F13, CSI + "25" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F14, CSI + "26" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F15, CSI + "28" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F16, CSI + "29" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F17, CSI + "31" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F18, CSI + "32" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F19, CSI + "23" + Char_07_14_Tilde);
            DoTest(ConsoleKey.F20, CSI + "34" + Char_07_14_Tilde);
        }

        [Test]
        [Explicit]
        public void All()
        {
            for (ConsoleKey i = 0; i < checked((ConsoleKey)256); ++i) {
                // Ignore un-defined ConsoleKey values.
                string name = i.ToString();
                string numName = ((int)i).ToString(System.Globalization.CultureInfo.InvariantCulture);
                if (name == numName) {
                    // Not a defined ConsoleKey value.
                    continue;
                }
                // Ignore keys with normal values (a-z, 0-9, space, etc).
                // TODO Ignore keys with normal values (a-z, 0-9, space, etc).
                ConsoleKey[] haveControlInstantiation = { ConsoleKey.Spacebar, ConsoleKey.Tab,
                    ConsoleKey.Backspace };
                ConsoleKey[] windowsOnly = { ConsoleKey.LeftWindows, ConsoleKey.RightWindows,
                    ConsoleKey.Applications, };
                if ((i >= ConsoleKey.A && i <= ConsoleKey.Z)
                        || (i >= ConsoleKey.D0 && i <= ConsoleKey.D9)
                        || (-1 != Array.IndexOf(haveControlInstantiation, i))
                        || (-1 != Array.IndexOf(windowsOnly, i))
                        ) {
                    continue;
                }
                //
                Execute(i);
            }
        }

        class AnsiConsoleDut : AnsiConsoleBase
        {
            public AnsiConsoleDut()
                : base(null)
            {
            }

            protected override void OnAnsiCommand(char command, int[] vars)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            protected override void OnText(byte[] text, int offset, int count)
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }
    }
}
